﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BackTask.Core.Extensions;
using BackTask.DashBoard.Abstractions;
using BackTask.DashBoard.Models;
using BackTask.Model;
using Quartz;
using Quartz.Impl.Matchers;

namespace BackTask.DashBoard.Lisenters
{
    public class JobManager : IJobManger
    {
        private static IJobManger _singleton = null;
        private static readonly object _lock = new object();
        private BaseTaskJobBuilder baseTaskJobBuilder;

        public static IJobManger CreateInstance()
        {
            if (_singleton == null)
            {
                lock (_lock)
                {
                    if (_singleton == null)
                    {
                        _singleton = new JobManager();
                    }
                }
            }
            return _singleton;
        }

        public async void DeleteJob(string name, string groupName)
        {
            IScheduler scheduler = this.baseTaskJobBuilder.GetScheduler();
            JobKey jobKey = new JobKey(name, groupName);
            bool isExists = await scheduler.CheckExists(jobKey);
            if (isExists)
            {
                scheduler.DeleteTaskJob(jobKey);
                this.baseTaskJobBuilder.GetTaskConfiguration().DeleteConfiguration(name, groupName);
            }
        }

        public async Task<IEnumerable<ScheduledJob>> GetAllJob()
        {
            IScheduler scheduler = this.baseTaskJobBuilder.GetScheduler();
            BackTaskConfiguration jsonConfiguration = this.baseTaskJobBuilder.GetTaskConfiguration();
            var jobs = await scheduler.GetJobKeys(GroupMatcher<JobKey>.AnyGroup());
            var scheduledJobs = new List<ScheduledJob>();
            foreach (var jobKey in jobs)
            {
                IJobDetail jobDetail = await scheduler.GetJobDetail(jobKey);
                ITrigger trigger = await scheduler.GetJobTrigger(jobKey);
                var _memJob = jsonConfiguration.GetJob(jobKey.Name, jobKey.Group);
                var @job = await GetScheduledJob(scheduler, jobDetail, trigger, _memJob);
                scheduledJobs.Add(@job);
            }

            return scheduledJobs;
        }

        public async Task<ScheduledJob> EditJob(CreateJobRequest createJobRequest)
        {
            IScheduler scheduler = this.baseTaskJobBuilder.GetScheduler();
            TaskJob job = TransformRequest2TaskJob(createJobRequest);
            this.baseTaskJobBuilder.GetTaskConfiguration().UpdateConfiguration(job);
            scheduler.UpdateTaskJob(job);
            return await TransformCreateJobRequest2ScheduledJob(createJobRequest, job);
        }

        private async Task<ScheduledJob> TransformCreateJobRequest2ScheduledJob(CreateJobRequest createJobRequest, TaskJob job)
        {
            IScheduler scheduler = this.baseTaskJobBuilder.GetScheduler();
            JobKey jobKey = new JobKey(createJobRequest.Name, createJobRequest.GroupName);
            IJobDetail jobDetail = await scheduler.GetJobDetail(jobKey);
            ITrigger trigger = await scheduler.GetJobTrigger(jobKey);
            return await GetScheduledJob(scheduler, jobDetail, trigger, job);
        }

        public async Task<ScheduledJob> CreateJob(CreateJobRequest createJobRequest)
        {
            IScheduler scheduler = this.baseTaskJobBuilder.GetScheduler();
            var exists = await scheduler.CheckExists(new JobKey(createJobRequest.Name, createJobRequest.GroupName));
            if (exists)
            {
                throw new Exception($"name: {createJobRequest.Name},groupName: {createJobRequest.GroupName}, job has been added");
            }

            TaskJob job = TransformRequest2TaskJob(createJobRequest);

            this.baseTaskJobBuilder.GetTaskConfiguration().AddConfiguration(job);

            scheduler.AddTaskJob(job);

            return await TransformCreateJobRequest2ScheduledJob(createJobRequest, job);
        }

        private static TaskJob TransformRequest2TaskJob(CreateJobRequest createJobRequest)
        {
            TaskJob job;
            if (createJobRequest.Type == TaskBuilderTypes.Cron)
            {
                job = new CronTaskJob(createJobRequest.Cron, createJobRequest.Url, createJobRequest.Name, createJobRequest.Priority, createJobRequest.Description, createJobRequest.GroupName);
            }
            else
            {
                SimpleTaskExecuteConfig config = new SimpleTaskExecuteConfig() { ExecuteCount = createJobRequest.ExecuteCount, Interval = createJobRequest.Interval, IntervalType = createJobRequest.IntervalType, RepeatForever = createJobRequest.RepeatForever };
                job = new SimpleTaskJob(createJobRequest.Url, createJobRequest.Name, config, createJobRequest.Priority, createJobRequest.Description, createJobRequest.GroupName);
            }

            return job;
        }

        private static async Task<ScheduledJob> GetScheduledJob(IScheduler scheduler, IJobDetail jobDetail, ITrigger trigger, object jobExecuteDetail)
        {
            JobKey key = jobDetail.Key;
            string description = jobDetail.Description;
            var startTime = trigger.StartTimeUtc;
            var nextFireTime = trigger.GetNextFireTimeUtc();
            var previousFireTime = trigger.GetPreviousFireTimeUtc();
            var endFireTime = trigger.EndTimeUtc;
            var state = await scheduler.GetTriggerState(trigger.Key);
            ScheduledJob scheduledJob = new ScheduledJob()
            {
                Name = key.Name,
                Description = description,
                GroupName = key.Group,
                StartTime = startTime,
                EndTime = endFireTime,
                PreviousFireTime = previousFireTime,
                NextFireTime = nextFireTime,
                State = state,
                Type = jobExecuteDetail.GetType() == typeof(CronTaskJob) ? TaskBuilderTypes.Cron.ToString().ToLower() : TaskBuilderTypes.Simple.ToString().ToLower(),
                Url = jobDetail.JobDataMap.GetString("url"),
                JobDetail = jobExecuteDetail
            };

            return scheduledJob;
        }

        public async void PauseJob(string name, string groupName)
        {
            IScheduler scheduler = this.baseTaskJobBuilder.GetScheduler();
            JobKey jobKey = new JobKey(name, groupName);
            bool isExists = await scheduler.CheckExists(jobKey);
            if (isExists)
            {
                scheduler.PauseTaskJob(jobKey);
            }
        }

        public async void RescheduleJob(string name, string groupName)
        {
            IScheduler scheduler = this.baseTaskJobBuilder.GetScheduler();
            JobKey jobKey = new JobKey(name, groupName);
            bool isExists = await scheduler.CheckExists(jobKey);
            if (isExists)
            {
                scheduler.RescheduleTaskJob(new JobKey(name, groupName));
            }
        }

        public async void StartJob(string name, string groupName)
        {
            IScheduler scheduler = this.baseTaskJobBuilder.GetScheduler();
            JobKey jobKey = new JobKey(name, groupName);
            bool isExists = await scheduler.CheckExists(jobKey);
            if (isExists)
            {
                scheduler.ResumeTaskJob(jobKey);
            }
        }

        public void UseJobBuilder(BaseTaskJobBuilder baseTaskJobBuilder)
        {
            this.baseTaskJobBuilder = baseTaskJobBuilder ?? throw new ArgumentNullException("baseTaskJobBuilder cannot be null");
        }
    }
}
